﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using RRSimulator.BlobManager.Lib.Model;
using RRSimulator.BlobManager.Lib.ServiceClient.Requests;
using RRSimulator.BlobManager.Lib.ServiceClient.Responses;

namespace RRSimulator.BlobManager.Lib
{
    public delegate void ProcessCompletedEventHandler(object sender, string ElapsedTime);

    public class BlobManagerRunner : IBlobManagerRunner
    {
        #region Constants

        private const string COMMON_FILE_TYPE = "application/octet-stream";
        private const int BLOCK_SIZE = 512 * 1024;
        private const uint MAX_RETRY_TIMES = 3;

        #endregion

        #region Fields

        private IList<FileModel> _fileInfos;
        private IList<BlocksModel> _uncommittedBlockInfos;
        private BlobManagerClient _serviceClient;
        private Stopwatch _stopwatch;

        #endregion

        #region Constructors

        public BlobManagerRunner(string containerName, OperationMode mode, UploadDataMode dataMode)
            : this()
        {
            ContainerName = containerName;
            Mode = mode;
            DataMode = dataMode;
        }

        private BlobManagerRunner()
        {
            _serviceClient = new BlobManagerClient();
            _fileInfos = new List<FileModel>();
            _uncommittedBlockInfos = new List<BlocksModel>();
            _stopwatch = new Stopwatch();

            _serviceClient.UploadFileCompleted += ServiceClient_UploadFileCompleted;
            _serviceClient.UploadFileStreamCompleted += ServiceClient_UploadFileStreamCompleted;
            _serviceClient.CommitBlocksCompleted += ServiceClient_CommitBlocksCompleted;
            _serviceClient.DeleteFileCompleted += ServiceClient_DeleteFileCompleted;
            _serviceClient.UploadBlockCompleted += ServiceClient_UploadBlockCompleted;
            _serviceClient.UploadBlockStreamCompleted += ServiceClient_UploadBlockStreamCompleted;
        }

        #endregion

        public event ProcessCompletedEventHandler ProcessCompleted;

        #region Properties

        public string ContainerName { get; private set; }
        public OperationMode Mode { get; set; }
        public UploadDataMode DataMode { get; set; }

        #endregion

        #region IBlobManagerRunner Implementation

        public void LoadFiles(IEnumerable<string> filePaths)
        {
            _fileInfos.Clear();

            if (filePaths != null)
            {
                int fileNumbers = filePaths.Count();
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Console.WriteLine("Loading {0} files...", fileNumbers);
                foreach (var filePath in filePaths)
                {
                    if (!string.IsNullOrEmpty(filePath))
                    {
                        FileModel fileInfo = GetFileInfo(filePath);

                        if (fileInfo != null)
                        {
                            _fileInfos.Add(fileInfo);
                        }
                    }
                }
                stopwatch.Stop();
                Console.WriteLine("{0} Files loaded in {1}", fileNumbers, string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff")));
            }
        }

        public void UploadFiles()
        {
            if (_fileInfos != null && _fileInfos.Count > 0)
            {
                int fileCount = _fileInfos.Count();
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Console.WriteLine();
                Console.WriteLine("Uploading Files...");
                foreach (var fileInfo in _fileInfos)
                {
                    UploadFile(fileInfo.FileName);                
                }
                stopwatch.Stop();
                Console.WriteLine();
                Console.WriteLine("{0} Files uploaded in {1}", fileCount, string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff")));
            }
            else
            {
                Console.WriteLine("No file has been loaded.");
            }
        }

        public void UploadFile(string fileName)
        {
            if (_fileInfos != null && _fileInfos.Count > 0)
            {
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Console.WriteLine();
                Console.WriteLine("Uploading File '{0}'...", fileName);
                try
                {
                    var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase));
                    long fileLength = 0;

                    if (fileInfo != null)
                    {
                        // initialized file info
                        fileInfo.CurrentRetryTimes = 0;
                        fileInfo.IsProcessing = true;

                        if (DataMode == UploadDataMode.Bytes)
                        {
                            fileInfo.FileBytes = GetFileBytes(fileInfo.FilePath);
                            fileLength = fileInfo.FileBytes != null ? fileInfo.FileBytes.Length : 0;
                        }
                        else if (DataMode == UploadDataMode.Stream)
                        {
                            fileInfo.MemoryMappedFile = GetFileMemoryMappedFile(fileInfo.FilePath);
                            if (fileInfo.MemoryMappedFile != null)
                            {
                                using (var fileStream = fileInfo.MemoryMappedFile.CreateViewStream())
                                {
                                    fileLength = fileStream.Length;
                                }
                            }
                        }

                        if (fileInfo.FileBytes != null || fileInfo.MemoryMappedFile != null)
                        {
                            if (fileLength <= (2 * 1024 * 1024))
                            {
                                // Upload File
                                Console.WriteLine("Uploading File...");
                                if (DataMode == UploadDataMode.Bytes)
                                {
                                    UploadFileBytes(fileInfo);
                                }
                                else
                                {
                                    UploadFileStream(fileInfo);
                                }
                            }
                            else
                            {
                                // Upload Blob
                                Console.WriteLine("Uploading Blocks...");
                                int curId, curIdx;
                                BlocksModel blocksInfo = new BlocksModel();
                                blocksInfo.FileBlocksCompleted += BlocksInfo_FileBlocksCompleted;
                                blocksInfo.FileName = fileInfo.FileName;

                                if (Mode == OperationMode.Async)
                                {
                                    _uncommittedBlockInfos.Add(blocksInfo);
                                }

                                blocksInfo.FileBlocksState = FileBlocksState.Uploading;

                                for (curIdx = 0, curId = 0; curIdx < fileLength; curIdx += BLOCK_SIZE, curId++)
                                {
                                    BlockModel blockInfo = new BlockModel();
                                    blockInfo.BlockId = Convert.ToBase64String(System.BitConverter.GetBytes(curId));
                                    blockInfo.BlockSize = (curIdx + BLOCK_SIZE) >= fileLength ? (int)fileLength - curIdx : BLOCK_SIZE;
                                    blockInfo.StartIndex = curIdx;

                                    blocksInfo.Add(blockInfo);
                                    Console.WriteLine("Uploading Block...");
                                    if (DataMode == UploadDataMode.Bytes)
                                    {
                                        UploadBlockBytes(fileInfo, blockInfo);
                                    }
                                    else if (DataMode == UploadDataMode.Stream)
                                    {
                                        UploadBlockStream(fileInfo, blockInfo);
                                    }



                                    if (blockInfo.IsFailed)
                                    {
                                        Console.WriteLine("Uploading Block Failed.");
                                        break;
                                    }
                                }

                                blocksInfo.FileBlocksState = FileBlocksState.Uploaded;

                                if (Mode == OperationMode.Sync)
                                {
                                    if (blocksInfo.IsFailed)
                                    {
                                        Console.WriteLine("Failed to upload File {0}.", fileInfo.FileName);
                                    }
                                    else
                                    {
                                        CommitBlocks(fileInfo, blocksInfo);
                                    }
                                    
                                    blocksInfo.Clear();
                                }
                            }

                            if (Mode == OperationMode.Sync)
                            {
                                if (DataMode == UploadDataMode.Bytes)
                                {
                                    fileInfo.FileBytes = null;
                                }
                                else
                                {
                                    fileInfo.MemoryMappedFile.Dispose();
                                    fileInfo.MemoryMappedFile = null;
                                }

                                fileInfo.IsProcessing = false;
                            }

                            stopwatch.Stop();
                            Console.WriteLine("File Upload '{0}' completed in {1}", fileName, string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff")));
                        }
                    }
                    else
                    {
                        stopwatch.Stop();
                        Console.WriteLine();
                        Console.WriteLine("File {0} doesn't exist in loaded files.");
                    }
                }
                catch (Exception ex)
                {
                    stopwatch.Stop();
                    Console.WriteLine();
                    Console.WriteLine("Error While Uploading file.");
                    Console.WriteLine("File Path : {0}", fileName);
                    Console.WriteLine("Error Message : {0}", ex.Message);
                }
            }
            else
            {
                Console.WriteLine("No file has been loaded.");
            }
        }

        public void ClearFiles()
        {
            if (_fileInfos != null && _fileInfos.Count > 0)
            {
                _fileInfos.Clear();
            }
            else
            {
                Console.WriteLine("No file has been loaded.");
            }
        }

        public void ClearBlobs()
        {
            if (_fileInfos != null && _fileInfos.Count > 0)
            {
                foreach (var fileInfo in _fileInfos)
                {
                    DeleteFile(fileInfo);
                }
            }
            else
            {
                Console.WriteLine("No file has been loaded.");
            }
        }

        public void DeleteBlob(string blobName)
        {
            if (_fileInfos != null && _fileInfos.Count > 0)
            {
                var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(blobName, StringComparison.CurrentCultureIgnoreCase));
                Stopwatch stopwatch = new Stopwatch();
                stopwatch.Start();
                Console.WriteLine();
                Console.WriteLine("Deleting Blob '{0}'...", blobName);
                
                // initialize file info
                fileInfo.CurrentRetryTimes = 0;
                fileInfo.IsProcessing = true;

                DeleteFile(fileInfo);

                if (Mode == OperationMode.Sync)
                {
                    fileInfo.IsProcessing = false;
                }

                stopwatch.Stop();
                Console.WriteLine("Delete Blob '{0}' Completed in {1}", blobName, string.Format("{0}:{1}", Math.Floor(stopwatch.Elapsed.TotalMinutes), stopwatch.Elapsed.ToString("ss\\.ff")));
            }
            else
            {
                Console.WriteLine("No file has been loaded.");
            }
        }

        #endregion

        #region Event Handlers

        private void BlocksInfo_FileBlocksCompleted(object sender, string fileName)
        {
            var blocksInfo = _uncommittedBlockInfos.FirstOrDefault(b => b.FileName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase));
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(fileName, StringComparison.CurrentCultureIgnoreCase));

            if (blocksInfo.IsFailed)
            {
                Console.WriteLine("Failed to upload Async File {0}.", fileInfo.FileName);
            }
            else
            {
                CommitBlocks(fileInfo, blocksInfo);
            }
        }

        private void ServiceClient_CommitBlocksCompleted(object sender, CommitBlocksCompletedEventArgs e)
        {
            var blocksInfo = _uncommittedBlockInfos.FirstOrDefault(b => b.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));

            if (blocksInfo == null || fileInfo == null)
                return;

            if (!e.Result.IsError)
            {
                Console.WriteLine("WCF Commit Blocks Async '{0}' process time : {1}", e.Result.BlobName, e.Result.ElapsedTime);
            }
            else
            {
                if (fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                {
                    ++fileInfo.CurrentRetryTimes;
                    CommitBlocks(fileInfo, blocksInfo);
                    return;
                }
                else
                {
                    Console.WriteLine("WCF Commit Blocks Async '{0}' process time : {1}", e.Result.BlobName, e.Result.ElapsedTime);
                }
            }

            blocksInfo.Clear();
            _uncommittedBlockInfos.Remove(blocksInfo);

            if (DataMode == UploadDataMode.Bytes)
            {
                fileInfo.FileBytes = null;
            }
            else
            {
                fileInfo.MemoryMappedFile.Dispose();
                fileInfo.MemoryMappedFile = null;
            }

            fileInfo.IsProcessing = false;

            StopStopwatch();
        }

        private void ServiceClient_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
        {
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));

            if (!e.Result.IsError)
            {
                Console.WriteLine("WCF Upload File Async '{0}' process time : {1}", e.Result.BlobName, e.Result.ElapsedTime);
            }
            else
            {
                if (fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                {
                    ++fileInfo.CurrentRetryTimes;
                    UploadFileBytes(fileInfo);
                    return;
                }
                else
                {
                    Console.WriteLine("WCF Upload File Async '{0}' error : {1}", e.Result.BlobName, e.Result.ErrorMessage);
                }
            }

            fileInfo.FileBytes = null;
            fileInfo.IsProcessing = false;

            StopStopwatch();
        }

        private void ServiceClient_UploadBlockStreamCompleted(object sender, UploadBlockStreamCompletedEventArgs e)
        {
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result, StringComparison.CurrentCultureIgnoreCase));
            var blockInfos = _uncommittedBlockInfos.FirstOrDefault(b => b.FileName.Equals(e.Result, StringComparison.CurrentCultureIgnoreCase));
            var blockInfo = blockInfos.FirstOrDefault(b => b.BlockId == e.BlockId);

            if (!e.IsError)
            {
                Console.WriteLine("WCF Upload Async '{0}' block (Id={1}) process time : {2}", e.Result, blockInfo.BlockId, e.ElapsedTime);
                blockInfo.IsFailed = false;
            }
            else
            {
                if (blockInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                {
                    ++blockInfo.CurrentRetryTimes;
                    UploadBlockStream(fileInfo, blockInfo);
                    return;
                }
                else
                {
                    Console.WriteLine("WCF Upload Async '{0}' block (Id={1}) error : {2}", e.Result, blockInfo.BlockId, e.ErrorMessage);
                    blockInfo.IsFailed = true;
                }
            }

            blockInfo.IsCompleted = true;
        }

        private void ServiceClient_UploadBlockCompleted(object sender, UploadBlockCompletedEventArgs e)
        {
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));
            var blockInfos = _uncommittedBlockInfos.FirstOrDefault(b => b.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));
            var blockInfo = blockInfos.FirstOrDefault(b => b.BlockId == e.Result.BlockId);

            if (!e.Result.IsError)
            {
                Console.WriteLine("WCF Upload Async '{0}' block (Id={1}) process time : {2}", e.Result.BlobName, blockInfo.BlockId, e.Result.ElapsedTime);
                blockInfo.IsFailed = false;
            }
            else
            {
                if (blockInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                {
                    ++blockInfo.CurrentRetryTimes;
                    UploadBlockBytes(fileInfo, blockInfo);
                    return;
                }
                else
                {
                    Console.WriteLine("WCF Upload Async '{0}' block (Id={1}) error : {2}", e.Result.BlobName, blockInfo.BlockId, e.Result.ErrorMessage);
                    blockInfo.IsFailed = true;
                }
            }

            blockInfo.IsCompleted = true;
        }

        private void ServiceClient_DeleteFileCompleted(object sender, DeleteFileCompletedEventArgs e)
        {
            var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result.BlobName, StringComparison.CurrentCultureIgnoreCase));

            if (!e.Result.IsError)
            {
                Console.WriteLine("WCF Delete Blob Async '{0}' process time : {1}", e.Result.BlobName, e.Result.ElapsedTime);
            }
            else
            {
                if (fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                {
                    ++fileInfo.CurrentRetryTimes;
                    DeleteFile(fileInfo);
                    return;
                }
                else
                {
                    Console.WriteLine("WCF Delete Blob Async '{0}' failed : {1}", e.Result.BlobName, e.Result.ErrorMessage);
                }
            }

            fileInfo.IsProcessing = false;

            StopStopwatch();
        }

        private void ServiceClient_UploadFileStreamCompleted(object sender, UploadFileStreamCompletedEventArgs e)
        {
            if (e.Result != null)
            {
                var fileInfo = _fileInfos.FirstOrDefault(f => f.FileName.Equals(e.Result, StringComparison.CurrentCultureIgnoreCase));

                if (!e.IsError)
                {
                    Console.WriteLine("WCF Upload File process time : {0}", e.ElapsedTime);
                }
                else
                {
                    if (fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES)
                    {
                        ++fileInfo.CurrentRetryTimes;
                        UploadFileStream(fileInfo);
                        return;
                    }
                    else
                    {
                        Console.WriteLine("WCF Upload File error : {0}", e.ErrorMessage);
                    }
                }

                fileInfo.MemoryMappedFile.Dispose();
                fileInfo.MemoryMappedFile = null;
                fileInfo.IsProcessing = false;

                StopStopwatch();
            }
        }

        #endregion

        #region Private Methods

        private void CommitBlocks(FileModel fileInfo, BlocksModel blocksInfo)
        {
            if (blocksInfo.IsFailed)
            {
                Console.WriteLine("Failed to upload file {0}.", fileInfo.FileName);
            }
            else
            {
                CommitBlocksRequest request = new CommitBlocksRequest();
                request.BlobName = blocksInfo.FileName;
                request.ContentType = fileInfo.FileType;
                request.ContainerName = ContainerName;
                request.BlockIds = blocksInfo.Select(b => b.BlockId).ToArray();
                request.HashValue = fileInfo.FileHash;

                if (Mode == OperationMode.Sync)
                {

                    CommitBlocksResponse response = null;

                    do
                    {
                        fileInfo.CurrentRetryTimes += response == null ? 0 : 1;
                        response = _serviceClient.CommitBlocks(request);
                    } while (response.IsError && fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                    if (response.IsError)
                    {
                        Console.WriteLine("Error on committing the {0}'s blocks : {1}", response.BlobName, response.ErrorMessage);
                    }
                }
                else if(Mode == OperationMode.Async)
                {
                    _serviceClient.CommitBlocksAsync(request);
                }
            }
        }

        private void UploadFileBytes(FileModel fileInfo)
        {
            UploadFileRequest request = new UploadFileRequest() { ContainerName = ContainerName };
            UploadFileResponse response = null;
            
            request.BlobName = fileInfo.FileName;
            request.ContentType = fileInfo.FileType;
            request.FileBytes = fileInfo.FileBytes;
            request.HashValue = fileInfo.FileHash;

            if (Mode == OperationMode.Sync)
            {
                do
                {
                    fileInfo.CurrentRetryTimes += response == null ? 0 : 1;
                    response = _serviceClient.UploadFile(request);
                } while (response.IsError && fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                if (!response.IsError)
                {
                    Console.WriteLine("WCF Upload File process time : {0}", response.ElapsedTime);
                }
                else
                {
                    Console.WriteLine("WCF Upload File error : {0}", response.ErrorMessage);
                }
            }
            else if (Mode == OperationMode.Async)
            {
                _serviceClient.UploadFileAsync(request);
            }
        }

        private void UploadFileStream(FileModel fileInfo)
        {
            UploadFileStreamRequest request = new UploadFileStreamRequest() { ContainerName = ContainerName };
            UploadFileStreamResponse response = null;

            request.BlobName = fileInfo.FileName;
            request.ContentType = fileInfo.FileType;
            request.FileStream = fileInfo.MemoryMappedFile.CreateViewStream();
            request.HashValue = fileInfo.FileHash;

            if (Mode == OperationMode.Sync)
            {
                response = new UploadFileStreamResponse();
                do
                {
                    fileInfo.CurrentRetryTimes += response == null ? 0 : 1;
                    response.ElapsedTime = _serviceClient.UploadFileStream(ref request.BlobName, request.ContainerName, request.ContentType, request.HashValue, request.FileStream, out response.ErrorMessage, out response.IsError);
                    response.BlobName = request.BlobName;
                } while (response.IsError && fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                if (!response.IsError)
                {
                    Console.WriteLine("WCF Upload File process time : {0}", response.ElapsedTime);
                }
                else
                {
                    Console.WriteLine("WCF Upload File error : {0}", response.ErrorMessage);
                }
            }
            else if (Mode == OperationMode.Async)
            {
                _serviceClient.UploadFileStreamAsync(request.BlobName, request.ContainerName, request.ContentType, request.HashValue, request.FileStream);
            }
        }

        private void UploadBlockBytes(FileModel fileInfo, BlockModel blockInfo)
        {
            UploadBlockRequest request = new UploadBlockRequest() { ContainerName = ContainerName };
            UploadBlockResponse response = null;

            request.BlobName = fileInfo.FileName;
            request.BlockId = blockInfo.BlockId;
            request.BlockBytes = new byte[blockInfo.BlockSize];
            Array.Copy(fileInfo.FileBytes, blockInfo.StartIndex, request.BlockBytes, 0, blockInfo.BlockSize);

            if (Mode == OperationMode.Sync)
            {
                do
                {
                    blockInfo.CurrentRetryTimes += response == null ? 0 : 1;
                    response = _serviceClient.UploadBlock(request);
                } while (response.IsError && blockInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                if (!response.IsError)
                {
                    Console.WriteLine("WCF Upload block (Id={0}) process time : {1}", blockInfo.BlockId, response.ElapsedTime);
                    blockInfo.IsFailed = false;
                }
                else
                {
                    Console.WriteLine("WCF Upload block (Id={0}) error : {1}", blockInfo.BlockId, response.ErrorMessage);
                    blockInfo.IsFailed = true;
                }

                blockInfo.IsCompleted = true;
            }
            else if (Mode == OperationMode.Async)
            {
                _serviceClient.UploadBlockAsync(request);
            }
        }

        private void UploadBlockStream(FileModel fileInfo, BlockModel blockInfo)
        {
            UploadBlockStreamRequest request = new UploadBlockStreamRequest() { ContainerName = ContainerName };
            UploadBlockStreamResponse response = null;

            request.BlobName = fileInfo.FileName;
            request.BlockId = blockInfo.BlockId;
            request.BlockStream = fileInfo.MemoryMappedFile.CreateViewStream(blockInfo.StartIndex, blockInfo.BlockSize);
            
            if (Mode == OperationMode.Sync)
            {
                response = new UploadBlockStreamResponse();
                do
                {
                    blockInfo.CurrentRetryTimes += response == null ? 0 : 1;
                    response.ElapsedTime = _serviceClient.UploadBlockStream(ref request.BlobName, ref request.BlockId, request.ContainerName, request.BlockStream, out response.ErrorMessage, out response.IsError);
                    response.BlobName = request.BlobName;
                    response.BlockId = request.BlockId;
                } while (response.IsError && blockInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                if (!response.IsError)
                {
                    Console.WriteLine("WCF Upload block (Id={0}) process time : {1}", blockInfo.BlockId, response.ElapsedTime);
                    blockInfo.IsFailed = false;
                }
                else
                {
                    Console.WriteLine("WCF Upload block (Id={0}) error : {1}", blockInfo.BlockId, response.ErrorMessage);
                    blockInfo.IsFailed = true;
                }

                blockInfo.IsCompleted = true;
            }
            else if (Mode == OperationMode.Async)
            {
                _serviceClient.UploadBlockStreamAsync(request.BlobName, request.BlockId, request.ContainerName, request.BlockStream);
            }
        }

        private void DeleteFile(FileModel fileInfo)
        {
            DeleteFileRequest request = new DeleteFileRequest() { ContainerName = ContainerName };

            request.BlobName = fileInfo.FileName;

            if (Mode == OperationMode.Sync)
            {
                DeleteFileResponse response = null;
                do
                {
                    fileInfo.CurrentRetryTimes += response == null ? 0 : 1;
                    response = _serviceClient.DeleteFile(request);
                } while (response.IsError && fileInfo.CurrentRetryTimes < MAX_RETRY_TIMES);

                if (!response.IsError)
                {
                    Console.WriteLine("WCF Delete Blob '{0}' process time : {1}", fileInfo.FileName, response.ElapsedTime);
                }
                else
                {
                    Console.WriteLine("WCF Delete Blob '{0}' failed : {1}", fileInfo.FileName, response.ErrorMessage);
                }
            }
            else
            {
                _serviceClient.DeleteFileAsync(request);
            }
        }

        private byte[] GetFileBytes(string filePath)
        {
            byte[] retval = null;
            FileStream fs = null;

            try
            {
                fs = File.OpenRead(filePath);
                retval = new byte[fs.Length];
                fs.Read(retval, 0, (int)fs.Length);
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occurred while loading file bytes.");
                Console.WriteLine("File Path : {0}", filePath);
                Console.WriteLine("Error Message : {0}", ex.Message);
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                }
            }

            return retval;
        }

        private MemoryMappedFile GetFileMemoryMappedFile(string filePath)
        {
            MemoryMappedFile retval = null;

            try
            {
                retval = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open);
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occurred while loading file stream.");
                Console.WriteLine("File Path : {0}", filePath);
                Console.WriteLine("Error Message : {0}", ex.Message);
            }

            return retval;
        }

        private FileModel GetFileInfo(string filePath)
        {
            int idx = 0;
            FileStream fs = null;
            FileModel retval = null;

            try
            {
                idx = filePath.LastIndexOf('\\');
                fs = File.OpenRead(filePath);
                retval = new FileModel() { FilePath = filePath };

                retval.FileName = filePath.Substring(idx + 1);
                retval.FileType = COMMON_FILE_TYPE;
                retval.FileHash = HashFile(fs);
            }
            catch (Exception ex)
            {
                retval = null;

                Console.WriteLine("An error occurred while loading file info.");
                Console.WriteLine("File Path : {0}", filePath);
                Console.WriteLine("Error Message : {0}", ex.Message);
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();

                }
            }

            return retval;
        }

        private string HashFile(string filePath)
        {
            using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                return HashFile(fs);
            }
        }

        private string HashFile(Stream stream)
        {
            StringBuilder sb = new StringBuilder();

            if (stream != null)
            {
                stream.Seek(0, SeekOrigin.Begin);

                MD5 md5 = MD5CryptoServiceProvider.Create();
                byte[] hash = md5.ComputeHash(stream);
                foreach (byte b in hash)
                    sb.Append(b.ToString("x2"));

                stream.Seek(0, SeekOrigin.Begin);
            }

            return sb.ToString();
        }

        public void StartStopwatch()
        {
            if (!_stopwatch.IsRunning)
            {
                _stopwatch.Reset();
                _stopwatch.Start();
            }
        }

        public void StopStopwatch()
        {
            if (_uncommittedBlockInfos.Count == 0 && _fileInfos.FirstOrDefault(f => f.IsProcessing) == null)
            {
                _stopwatch.Stop();

                if (ProcessCompleted != null)
                {
                    ProcessCompleted(this, string.Format("{0}:{1}", Math.Floor(_stopwatch.Elapsed.TotalMinutes), _stopwatch.Elapsed.ToString("ss\\.ff")));
                }
            }
        }

        #endregion
    }

    public enum UploadDataMode { Bytes, Stream }
    public enum OperationMode { Sync, Async }
}
